grammar SqlStatement;

//options {
//    k=2;                                // needed for "&&" and "||"
//}

@lexer::header { 
package org.sqlproc.engine.impl;
}

@header { 
package org.sqlproc.engine.impl;
}

@lexer::members {
  private List<ErrorMsg> errors = new ArrayList<ErrorMsg>();
  public List<ErrorMsg> getErrors() {
    return errors;
  }

  public String getErrorMessage(RecognitionException e, String[] tokenNames) {
    String msg = super.getErrorMessage(e,tokenNames);
    errors.add(ParserUtils.create(msg,e,tokenNames));
    return msg;
  }
}

@members {
  public static void main(String[] args) throws Exception {
    SqlStatementLexer lex = new SqlStatementLexer(new ANTLRFileStream(args[0]));
    CommonTokenStream tokens = new CommonTokenStream(lex);

    SqlStatementParser parser = new SqlStatementParser(tokens);

    try {
      parser.parse();
    } catch (RecognitionException e)  {
      e.printStackTrace();
    }
  }

  private Token getLastToken() {
    return input.LT(-1);
  }

  private List<ErrorMsg> errors = new ArrayList<ErrorMsg>();
  public List<ErrorMsg> getErrors() {
    return errors;
  }

  @Override
  public void reportError(RecognitionException e) {
    String msg = super.getErrorMessage(e,tokenNames);
    errors.add(ParserUtils.create(msg,e,tokenNames));
  }
  
  String getText(Token token) {
    if (token == null)
      return null;
    return token.getText();
  }
  
  StringBuilder text;
  
  void add() {
    text.append(getLastToken().getText());
  }
  
  void addText(Object target) {
    ParserUtils.addText(target, text);
  }
  
  void addIdent(Object target, SqlMetaIdent ident) {
    ParserUtils.addIdent(target, ident, text);
  }
  
  SqlMetaIdent newIdent(Token ident, Token caseConversion) {
    return ParserUtils.newIdent(ident.getText(), getText(caseConversion));
  }
  
  void addConstant(Object target, SqlMetaConst cnst) {
    ParserUtils.addConstant(target, cnst, text);
  }
  
  SqlMetaConst newConstant(Token cnst, Token caseConversion) {
    return ParserUtils.newConstant(cnst.getText(), getText(caseConversion));
  }
  
  void addOperator(SqlMetaLogExpr expr, boolean isAnd) {
    if (isAnd)
      expr.addElement(new SqlMetaLogOperator(SqlMetaLogOperator.Type.AND));
    else
      expr.addElement(new SqlMetaLogOperator(SqlMetaLogOperator.Type.OR));
  }
}

parse returns [SqlMetaStatement stmt]
@init {$stmt = new SqlMetaStatement(); text = new StringBuilder();}
	: sql[$stmt] EOF 
	;

sql [SqlMetaStatement stmt]	
@after {addText(stmt);}
	:	
	// except LBRACE STRING COLON
	(IDENT_DOT | IDENT | WS | DIGIT | NUMBER | REST | HASH | NOT | AND | OR | DOT | UNDER | MINUS | PLUS | LPAREN | RPAREN | RBRACE | QUESTI | BAND | BOR) 
		{add();} sql[$stmt]?
	| COLON ident=identifier 
		{addIdent(stmt, ident);} sql[$stmt]?
     	| STRING cnst=constant 
     		{addConstant(stmt, cnst);} sql[$stmt]?
     	| LBRACE metaSql[$stmt] RBRACE sql[$stmt]?
     	;

metaSql [SqlMetaStatement stmt]
@init {SqlMetaAndOr dataAnd; SqlMetaIfItem dataIfItem; SqlMetaIf dataIf; addText(stmt);}	
	:
	// except LBRACE RBRACE QUESTI BAND BOR HASH
	(IDENT_DOT | IDENT | WS | DIGIT | NUMBER | REST | NOT | AND | OR | DOT | UNDER | MINUS | PLUS | LPAREN | RPAREN | STRING | COLON)
		{add(); dataAnd = new SqlMetaAndOr(SqlMetaAndOr.Type.NO);} item=andOrSql {dataAnd.addElement(item);} 
		(BOR item=andOrSql {dataAnd.addElement(item);})* {stmt.addElement(dataAnd);}
	| QUESTI {dataIf = new SqlMetaIf(); } expr=ifSqlCond {dataIf.setExpression(expr);} 
		BOR {dataIfItem = new SqlMetaIfItem();} ifSql[dataIfItem] {dataIf.addElement(dataIfItem);} 
		(BOR {dataIfItem = new SqlMetaIfItem();} ifSql[dataIfItem] {dataIf.addElement(dataIfItem);})*
		 {stmt.addElement(dataIf);}
	| BAND {dataAnd = new SqlMetaAndOr(SqlMetaAndOr.Type.AND);} item=andOrSql {dataAnd.addElement(item);} 
		(BOR item=andOrSql {dataAnd.addElement(item);})* {stmt.addElement(dataAnd);}
	| BOR {dataAnd = new SqlMetaAndOr(SqlMetaAndOr.Type.OR);} item= andOrSql {dataAnd.addElement(item);} 
		(BOR item=andOrSql {dataAnd.addElement(item);})* {stmt.addElement(dataAnd);}
	| HASH orderId=DIGIT sqlDataOrd=ordSql[Integer.parseInt(orderId.getText())] {stmt.addElement(sqlDataOrd);}
	;
	
ifSql [SqlMetaIfItem ifItem]
@after {addText(ifItem);}
	:
	// except LBRACE STRING COLON RBRACE BOR
	(IDENT_DOT | IDENT | WS | DIGIT | NUMBER | REST | HASH | NOT | AND | OR | DOT | UNDER | MINUS | PLUS | LPAREN | RPAREN | QUESTI | BAND)
		{add();} ifSql[$ifItem]?
	| COLON ident=identifier 
		{addIdent(ifItem, ident);} ifSql[$ifItem]?
	| STRING cnst=constant 
		{addConstant(ifItem, cnst);} ifSql[$ifItem]?
	| LBRACE ifMetaSql[$ifItem] RBRACE ifSql[$ifItem]?
	;
     	
ifMetaSql [SqlMetaIfItem ifItem]
@init {SqlMetaAndOr dataAnd; SqlMetaIfItem dataIfItem; SqlMetaIf dataIf; addText(ifItem);}	
	:
	// except LBRACE RBRACE QUESTI BAND BOR
	(IDENT_DOT | IDENT | WS | DIGIT | NUMBER | REST | NOT | AND | OR | DOT | UNDER | MINUS | PLUS | LPAREN | RPAREN | STRING | COLON | HASH)
		{add(); dataAnd = new SqlMetaAndOr(SqlMetaAndOr.Type.NO);} item=andOrSql {dataAnd.addElement(item);} 
		(BOR item=andOrSql {dataAnd.addElement(item);})* {ifItem.addElement(dataAnd);}
	| QUESTI {dataIf = new SqlMetaIf(); } expr=ifSqlCond {dataIf.setExpression(expr);} 
		BOR {dataIfItem = new SqlMetaIfItem();} ifSql[dataIfItem] {dataIf.addElement(dataIfItem);} 
		(BOR {dataIfItem = new SqlMetaIfItem();} ifSql[dataIfItem] {dataIf.addElement(dataIfItem);})*
		 {ifItem.addElement(dataIf);}
	| BAND {dataAnd = new SqlMetaAndOr(SqlMetaAndOr.Type.AND);} item=andOrSql {dataAnd.addElement(item);} 
		(BOR item=andOrSql {dataAnd.addElement(item);})* {ifItem.addElement(dataAnd);}
	| BOR {dataAnd = new SqlMetaAndOr(SqlMetaAndOr.Type.OR);} item= andOrSql {dataAnd.addElement(item);} 
		(BOR item=andOrSql {dataAnd.addElement(item);})* {ifItem.addElement(dataAnd);}
	;

ifSqlCond returns[SqlMetaLogExpr expr]
@init {$expr = new SqlMetaLogExpr();}
	:
	WS* ifSqlCondItem[$expr] WS* ((AND {addOperator(expr,true);} | OR {addOperator(expr,false);}) WS*
		ifSqlCondItem[$expr] WS*)*
	;
	
ifSqlCondItem [SqlMetaLogExpr expr]
	:
	not=NOT? COLON ident=identifier {ident.setNot(not!=null); addIdent(expr,ident);}
	| not=NOT? STRING cnst=constant {cnst.setNot(not!=null); addConstant(expr,cnst);}
	| not=NOT? LPAREN newExpr=ifSqlCond RPAREN {expr.addElement(newExpr);}
	;
	
andOrSql returns [SqlMetaAndOrItem item]
@init {$item = new SqlMetaAndOrItem();}
@after {addText(item);}
	:	
	// except BOR STRING COLON RBRACE
	(IDENT_DOT | IDENT | WS | DIGIT | NUMBER | REST | HASH | NOT | AND | OR | DOT | UNDER | MINUS | PLUS | LPAREN | RPAREN | QUESTI | BAND | LBRACE)
		{add();} simpleSql[$item]?
	| COLON ident=identifier 
		{addIdent(item, ident);} simpleSql[$item]?
	| STRING cnst=constant
		{addConstant(item, cnst);} simpleSql[$item]?
	;	
	
ordSql [int orderId] returns [SqlMetaOrd ord]
@init {$ord = new SqlMetaOrd(orderId);}
@after {addText(ord);}
 	:	
	// except STRING COLON RBRACE
	(IDENT_DOT | IDENT | WS | DIGIT | NUMBER | REST | HASH | NOT | AND | OR | DOT | UNDER | MINUS | PLUS | LPAREN | RPAREN | QUESTI | BAND | BOR | LBRACE)
		{add();} simpleSql[$ord]?
	| COLON ident=identifier 
		{addIdent(ord, ident);} simpleSql[$ord]?
	| STRING cnst=constant
		{addConstant(ord, cnst);} simpleSql[$ord]?
	;	
	
simpleSql [Object item]
@after {addText(item);}
	:	
	// except BOR STRING COLON RBRACE
	(IDENT_DOT | IDENT | WS | DIGIT | NUMBER | REST | HASH | NOT | AND | OR | DOT | UNDER | MINUS | PLUS | LPAREN | RPAREN | QUESTI | BAND | LBRACE)
		{add();} simpleSql[$item]?
	| COLON ident=identifier 
		{addIdent(item, ident);} simpleSql[$item]?
	| STRING cnst=constant
		{addConstant(item, cnst);} simpleSql[$item]?
	;	
	

constant returns [SqlMetaConst result]
	:	
	(caseCnst=PLUS | caseCnst=MINUS)? (cnst=IDENT_DOT | cnst=IDENT)
		{$result = newConstant(cnst, $caseCnst);}
		('^' IDENT { $result.setMetaType(getLastToken().getText()); }
		 ('^' (IDENT | NUMBER | DIGIT) { $result.getSqlType().setValue(getLastToken().getText()); }
		 )?
		)?
	;
identifier returns [SqlMetaIdent result]
	:	
	(caseIdent=PLUS | caseIdent=MINUS)? (ident=IDENT_DOT | ident=IDENT)
		{$result = newIdent($ident, $caseIdent);}
		('^' IDENT { $result.setMetaType(getLastToken().getText()); }
		 ('^' (IDENT | NUMBER | DIGIT) { $result.getSqlType().setValue(getLastToken().getText()); }
		 )?
		)?
	;


	
IDENT_DOT: IDENT (DOT IDENT)+;
IDENT: ('a'..'z' | 'A'..'Z') ('a'..'z' | 'A'..'Z' | '0'..'9' | '_' | '-')*;
NUMBER: DIGIT DIGIT+;
DIGIT: ('0'..'9');
WS:    (' ' | '\t')+ ;
REST:  ( '\"' | '%' | '\'' | '*' | ',' | '/' | ';'..'>' | '@' | '['..'^' | '`' | '~' )+ ;
AND:   '&' '&' ;
OR:    '|' '|' ;

COLON:    ':' ;
STRING:   '$' ;
DOT:      '.' ;
UNDER:    '_' ;
MINUS:    '-' ;
PLUS:     '+' ;
LPAREN:   '(' ;
RPAREN:   ')' ;
LBRACE:   '{' ;
RBRACE:   '}' ;
QUESTI:   '?' ;
NOT:      '!' ;
BAND:      '&' ;
BOR:       '|' ;
HASH:      '#' ;
